/*
 * Copyright (C) 2016, apexes.net. All rights reserved.
 * 
 *        http://www.apexes.net
 * 
 */
package net.apexes.wsonrpc.core;

import net.apexes.wsonrpc.core.exception.WsonrpcRuntimeException;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;

/**
 * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
 *
 */
public final class RemoteInvoker {
    
    public static RemoteInvoker create(Remote remote) {
        return new RemoteInvoker(remote);
    }

    private final Remote remote;
    private String serviceName;
    private boolean ignoreName = false;
    private boolean simpleName = false;
    private ClassLoader classLoader;
    private int timeout;
    private TraceGenerater traceGenerater;
    
    private RemoteInvoker(Remote remote) {
        this.remote = remote;
    }
    
    /**
     * 自定义服务名称
     * @param serviceName 服务名称
     * @return 返回当前实例
     */
    public RemoteInvoker serviceName(String serviceName) {
        if (serviceName == null) {
            return ignoreName();
        }
        this.serviceName = serviceName;
        this.ignoreName = false;
        this.simpleName = false;
        return this;
    }

    public RemoteInvoker ignoreName() {
        this.serviceName = null;
        this.ignoreName = true;
        this.simpleName = false;
        return this;
    }

    /**
     * 如果未设置服务名称就使用serviceClass的simpleName
     * @return 返回当前实例
     */
    public RemoteInvoker simpleName() {
        this.serviceName = null;
        this.ignoreName = false;
        this.simpleName = true;
        return this;
    }

    /**
     * 设置类加载器
     * @param classLoader 类加载器
     * @return 返回当前实例
     */
    public RemoteInvoker classLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
        return this;
    }

    /**
     * 设置超时时间，0表示永不超时。单位为TimeUnit.MILLISECONDS
     * 
     * @param timeout 超时毫秒数
     * @return 返回当前实例
     */
    public RemoteInvoker timeout(int timeout) {
        this.timeout = timeout;
        return this;
    }

    public RemoteInvoker trace(TraceGenerater traceGenerater) {
        this.traceGenerater = traceGenerater;
        return this;
    }

    /**
     * 获取指定类型的对象
     * @param serviceClass 要获取的服务类
     * @return 返回指定服务类的对象
     */
    @SuppressWarnings("unchecked")
    public <T> T get(final Class<T> serviceClass) {
        if (classLoader == null) {
            classLoader = serviceClass.getClassLoader();
        }
        InvocationHandler handler = new InvocationHandlerImpl(remote, serviceName(serviceClass), timeout, traceGenerater);
        return (T) Proxy.newProxyInstance(classLoader, new Class<?>[] { serviceClass }, handler);
    }

    private String serviceName(Class<?> serviceClass) {
        if (ignoreName) {
            return null;
        } else if (serviceName != null) {
            return serviceName;
        } else if (simpleName) {
            return serviceClass.getSimpleName();
        } else {
            return serviceClass.getName();
        }
    }

    /**
     * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
     *
     */
    private static class InvocationHandlerImpl implements InvocationHandler {

        private final Remote remote;
        private final String serviceName;
        private final int timeout;
        private final TraceGenerater traceGenerater;

        InvocationHandlerImpl(Remote remote, String serviceName, int timeout, TraceGenerater traceGenerater) {
            this.remote = remote;
            this.serviceName = serviceName;
            this.timeout = timeout;
            this.traceGenerater = traceGenerater;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getDeclaringClass() == Object.class) {
                return proxyObjectMethods(method, proxy, args);
            }
            try {
                String methodName;
                if (serviceName == null || serviceName.isEmpty()) {
                    methodName = method.getName();
                } else {
                    methodName = serviceName + "." + method.getName();
                }

                Type returnType = method.getGenericReturnType();
                if (returnType == void.class) {
                    String trace = null;
                    if (traceGenerater != null) {
                        trace = traceGenerater.trace();
                    }
                    remote.notify(methodName, args, trace);
                    return null;
                }
                return remote.request(methodName, args, returnType, timeout);
            } catch (RuntimeException e) {
                throw e;
            } catch (Exception e) {
                throw new WsonrpcRuntimeException(e.getMessage(), e);
            }
        }
    }

    private static Object proxyObjectMethods(Method method, Object proxyObject, Object[] args) {
        String name = method.getName();
        if ("toString".equals(name)) {
            return proxyObject.getClass().getName() + "@" + System.identityHashCode(proxyObject);
        }
        if ("hashCode".equals(name)) {
            return System.identityHashCode(proxyObject);
        }
        if ("equals".equals(name)) {
            return proxyObject == args[0];
        }
        throw new RuntimeException(method.getName() + " is not a member of java.lang.Object");
    }

}
